﻿#region

using System;
using System.Reflection;
using System.Reflection.Emit;

#endregion

namespace Toolbox.Dynamic
{
    public interface IDynamicAssembly
    {
        string Name { get; }
        string FileName { get; }
        IDynamicClass DefineDynamicClass(string name, Type parentType, params Type[] interfaces);
        IDynamicInterface DefineDynamicInterface(string name, params Type[] interfaces);
        void Save();
    }

    public class DynamicAssembly : IDynamicAssembly
    {
        private readonly AssemblyBuilder _assemblyBuilder;
        private readonly ModuleBuilder _moduleBuilder;
        private readonly string _name;

        public DynamicAssembly(string name) : this(name, AppDomain.CurrentDomain) {}

        public DynamicAssembly(string name, AppDomain appDomain)
        {
            _name = Contract.NotEmpty(name, "name");
            Contract.NotNull(appDomain, "appDomain");

            AssemblyName assemblyName = new AssemblyName(name);
            assemblyName.Version = new Version("1.0.0.0");

            AssemblyBuilderAccess assemblyAccess = AssemblyBuilderAccess.RunAndSave;

            _assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, assemblyAccess);
            _moduleBuilder = _assemblyBuilder.DefineDynamicModule(Name, FileName);
        }

        #region IDynamicAssembly Members

        public string Name
        {
            get { return _name; }
        }

        public string FileName
        {
            get { return Name + ".dll"; }
        }

        public void Save()
        {
            _assemblyBuilder.Save(FileName);
        }

        public IDynamicClass DefineDynamicClass(
            string name, Type parentType, params Type[] interfaces)
        {
            Contract.NotEmpty(name, "name");

            TypeBuilder typeBuilder = _moduleBuilder.DefineType(
                Name + "." + name, TypeAttributes.Class | TypeAttributes.Public, parentType,
                interfaces);
            return new DynamicClass(typeBuilder);
        }

        public IDynamicInterface DefineDynamicInterface(string name, params Type[] interfaces)
        {
            Contract.NotEmpty(name, "name");
            Contract.NoneNull(interfaces, "interfaces");
            Contract.IsTrueForAll(
                interfaces, "interfaces", "not an interface type",
                delegate(Type i) { return i.IsInterface; });

            TypeBuilder typeBuilder = _moduleBuilder.DefineType(
                Name + "." + name,
                TypeAttributes.Interface | TypeAttributes.Abstract | TypeAttributes.Public);

            foreach (Type i in interfaces)
                typeBuilder.AddInterfaceImplementation(i);

            return new DynamicClass(typeBuilder);
        }

        #endregion
    }
}